<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml" lang="en"><head>

  
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="title" content="symfony - open-source PHP web framework">
<meta name="robots" content="index, follow">
<meta name="description" content="symfony - open-source PHP web framework">
<meta name="keywords" content="symfony, project, framework, php, php5, open-source, mit, symphony">
<meta name="language" content="en">

    <link rel="stylesheet" type="text/css" media="all" href="15-Unit-and-Functional-Testing_files/main.css">
    <link rel="stylesheet" type="text/css" media="print" href="15-Unit-and-Functional-Testing_files/print.css">

    <title>The Definitive Guide to symfony | Chapter 15 - Unit And Functional Testing | symfony | Web PHP Framework</title>

    <link rel="alternate" type="application/rss+xml" title="Blog Entries" href="http://feeds.feedburner.com/symfony/blog">
    <link rel="alternate" type="application/rss+xml" title="Blog Comments" href="http://feeds.feedburner.com/symfony/blog/comments">
    <link rel="alternate" type="application/rss+xml" title="Forum Entries" href="http://forum.symfony-project.org/rdf.php?mode=m&amp;l=1&amp;n=10&amp;basic=1">
    <link rel="alternate" type="application/rss+xml" title="User Mailing-List Messages" href="http://groups.google.com/group/symfony-users/feed/atom_v1_0_msgs.xml">
    <link rel="alternate" type="application/rss+xml" title="Dev Mailing-List Messages" href="http://groups.google.com/group/symfony-devs/feed/atom_v1_0_msgs.xml">
    <link rel="alternate" type="application/rss+xml" title="Project Timeline" href="http://trac.symfony-project.org/timeline?milestone=on&amp;ticket=on&amp;ticket_details=on&amp;changeset=on&amp;wiki=on&amp;max=10&amp;daysback=10&amp;format=rss">

    <link rel="search" type="application/opensearchdescription+xml" href="http://www.symfony-project.org/api/1_0/opensearch.xml" title="symfony 1.0 API">
    <link rel="search" type="application/opensearchdescription+xml" href="http://www.symfony-project.org/api/1_2/opensearch.xml" title="symfony 1.2 API">

    <link rel="shortcut icon" href="http://www.symfony-project.org/favicon.ico">
  <link rel="stylesheet" type="text/css" media="all" href="15-Unit-and-Functional-Testing_files/highlight.css">
<link rel="stylesheet" type="text/css" media="all" href="15-Unit-and-Functional-Testing_files/sensiolabs.css">
</head><body>
    <table id="topbar" width="100%" cellpadding="0" cellspacing="0">
      <tbody><tr>
        <td id="header"><a href="http://www.symfony-project.org/"><img alt="symfony Web PHP framework" src="15-Unit-and-Functional-Testing_files/symfony_logo.gif"></a></td>
        <td id="navigation">
          <ul>
            <li><a href="http://www.symfony-project.org/about">About</a></li>
            <li><a href="http://www.symfony-project.org/installation">Installation</a></li>
            <li><a href="http://www.symfony-project.org/doc/1_2/">Documentation</a></li>
            <li><a href="http://www.symfony-project.org/plugins/">Plugins</a></li>
            <li><a href="http://www.symfony-project.org/community">Community</a></li>
            <li><a href="http://www.symfony-project.org/blog/">Blog</a></li>
            <li class="last"><a href="http://trac.symfony-project.org/timeline">Development</a></li>
          </ul>
        </td>
      </tr>
      <tr><td id="topseparator" colspan="2"></td></tr>
              <tr>
          <td colspan="2">
            <h1><a href="http://www.symfony-project.org/book/1_0/">The Definitive Guide to symfony</a></h1>
                          <h2>Chapter 15 - Unit And Functional Testing</h2>
                      </td>
        </tr>
          </tbody></table>

          <div id="bar">
          <h2>About</h2>

  <p>
    You are currently reading <em>"<a href="http://www.symfony-project.org/book/1_0/">The Definitive Guide to symfony</a>"</em> which is licensed under the GFDL license.
  </p>

  <div style="float: right;">
  <a href="http://www.amazon.com/dp/1590597869?tag=symfonyprojec-20&amp;camp=14573&amp;creative=327641&amp;linkCode=as1&amp;creativeASIN=1590597869&amp;adid=0E8EEASTSYS05JS23TXC&amp;"><img style="padding-left: 5px;" src="15-Unit-and-Functional-Testing_files/the_definitive_guide_to_symfony.jpg" width="80"></a>
</div>

  <div class="bar_block">
  <a href="http://www.symfony-live.com/"><img src="15-Unit-and-Functional-Testing_files/symfony_live_2010.jpg" alt="Symfony Live 2010 Paris Conference"></a>
</div>

    <h2>Chapter Content</h2>

  <div id="chapter_contents">
          <h3>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_automated_tests">Automated Tests</a>      </h3>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_unit_and_functional_tests">Unit and Functional Tests</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_test_driven_development">Test-Driven Development</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_the_lime_testing_framework">The Lime Testing Framework</a>      </h4>
          <h3>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_unit_tests">Unit Tests</a>      </h3>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_what_do_unit_tests_look_like">What Do Unit Tests Look Like?</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_unit_testing_methods">Unit Testing Methods</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_testing_parameters">Testing Parameters</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_the_test_unit_task">The test-unit Task</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_stubs_fixtures_and_autoloading">Stubs, Fixtures, and Autoloading</a>      </h4>
          <h3>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_functional_tests">Functional Tests</a>      </h3>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_what_do_functional_tests_look_like">What Do Functional Tests Look Like?</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_browsing_with_the_sftestbrowser_object">Browsing with the sfTestBrowser Object</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_using_assertions">Using Assertions</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_using_css_selectors">Using CSS Selectors</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_working_in_the_test_environment">Working in the Test Environment</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_the_test_functional_task">The test-functional Task</a>      </h4>
          <h3>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_test_naming_practices">Test Naming Practices</a>      </h3>
          <h3>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_special_testing_needs">Special Testing Needs</a>      </h3>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_executing_tests_in_a_test_harness">Executing Tests in a Test Harness</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_accessing_a_database">Accessing a Database</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_testing_the_cache">Testing the Cache</a>      </h4>
          <h4>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_sub_testing_interactions_on_the_client">Testing Interactions on the Client</a>      </h4>
          <h3>
        <a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing#chapter_15_summary">Summary</a>      </h3>
      </div>
  <h2>Navigate</h2>
<table width="100%">
  <tbody>
    <tr>
      <td>
        <ul class="book_keywords">
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Actions">action</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/14-Generators#Administration">admin generator</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/11-Ajax-Integration">ajax</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/19-Mastering-Symfony-s-Configuration-Files#Extending%20the%20Autoloading%20Feature">autoloading</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Batch%20Files">batch</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/12-Caching">cache</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/16-Application-Management-Tools">command line</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#Components">component</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/05-Configuring-Symfony">configuration</a></li>
          <!-- <li><a href="/book/1_0/">control panel</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Accessing%20The%20Request">cookie</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Action%20Security">credential</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/08-Inside-the-Model-Layer#Retrieving%20Records%20with%20Criteria">criteria</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/14-Generators#Scaffolding">crud</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/08-Inside-the-Model-Layer">database</a></li>
          <!-- <li><a href="/book/1_0/">dates</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/16-Application-Management-Tools#Debugging">debug</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/16-Application-Management-Tools#Deploying%20Applications">deployment</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/02-Exploring-Symfony-s-Code#File%20Tree%20Structure">directories</a></li>
          <!-- <li><a href="/book/1_0/">email</a></li> -->
          <!-- <li><a href="/book/1_0/">feed</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/05-Configuring-Symfony#Environments">environment</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#Output%20Escaping">escaping</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/10-Forms#Repopulating%20the%20Form">fillin</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Filters">filter</a></li>
          <!-- <li><a href="/book/1_0/">finder</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Flash%20Attributes">flash</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/10-Forms">form</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#The%20Front%20Controller">front controller</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/14-Generators">generator</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/13-I18n-and-L10n">i18n</a></li>
          <!-- <li><a href="/book/1_0/">iis</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/03-Running-Symfony">installation</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/11-Ajax-Integration#Basic%20JavaScript%20Helpers">javascript</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/13-I18n-and-L10n#Text%20Information%20in%20the%20Database">l10n</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#Page%20Layout">layout</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Linking%20to%20Another%20Action">link</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/16-Application-Management-Tools#Populating%20a%20Database">load data</a></li>
        </ul>
      </td>
      <td>
        <ul class="book_keywords">
          <li><a href="http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#View%20Configuration">meta</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/17-Extending-Symfony#Mixins">mixin</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/08-Inside-the-Model-Layer">model</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/13-I18n-and-L10n">multilinguism</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/02-Exploring-Symfony-s-Code#The%20MVC%20Pattern">mvc</a></li>
          <!-- <li><a href="/book/1_0/">numbers</a></li> -->
          <!-- <li><a href="/book/1_0/">pagination</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/02-Exploring-Symfony-s-Code#Parameter%20Holders">parameter</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#Partials">partial</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/03-Running-Symfony#Installing%20the%20Symfony%20PEAR%20Package">pear</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/18-Performance">performance</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/17-Extending-Symfony#Plug-Ins">plug-ins</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/16-Application-Management-Tools#Populating%20a%20Database">populate</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/08-Inside-the-Model-Layer">Propel</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Accessing%20the%20Request">request</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/09-Links-and-the-Routing-System">routing</a></li>
          <!-- <li><a href="/book/1_0/">rss</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/03-Running-Symfony#Installing%20the%20Sandbox">sandbox</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/14-Generators#Scaffolding">scaffolding</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/08-Inside-the-Model-Layer#Symfony%27s%20Database%20Schema">schema</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Action%20Security">security</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#User%20Session">session</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/19-Mastering-Symfony-s-Configuration-Files#Symfony%20Settings">settings</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/05-Configuring-Symfony#Accessing%20the%20Configuration%20from%20Code">sfConfig</a></li>
          <!-- <li><a href="/book/1_0/">shopping cart</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#Slots">slot</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/08-Inside-the-Model-Layer#Using%20Raw%20SQL%20Queries">sql</a></li>
          <!-- <li><a href="/book/1_0/">sortable list</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#Templating">templates</a></li>
          <!-- <li><a href="/book/1_0/">text</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/15-Unit-and-Functional-Testing">unit test</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Accessing%20the%20Request">upload</a></li>
          <!-- <li><a href="/book/1_0/">upload</a></li> -->
          <li><a href="http://www.symfony-project.org/book/1_0/09-Links-and-the-Routing-System">url</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Validation%20and%20Error-Handling%20Methods">validateXXX</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/10-Forms#Form%20Validation">validate form</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/10-Forms#Standard%20Symfony%20Validators">validators</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/07-Inside-the-View-Layer#View%20Configuration">view.yml</a></li>
          <li><a href="http://www.symfony-project.org/book/1_0/05-Configuring-Symfony#YAML%20Syntax%20and%20Symfony%20Conventions">yaml</a></li>
        </ul>
      </td>
    </tr>
  </tbody>
</table>

  <img style="margin-top: 10px;" src="15-Unit-and-Functional-Testing_files/symfony-training.gif" alt="symfony training"><div id="next_training_session">
  <div style="font-size: 90%; text-align: center; padding-bottom: 10px;"><em>Be trained by symfony experts</em></div>
      <div>
      <a href="http://www.sensiolabs.com/en/training/detail/SYMFONY-DOCTRINE-PARIS-DECEMBER-2009">Dec 16</a>:
      Paris      <span style="font-size: 80%;">(1.3 + Doctrine - Français)</span>
    </div>
      <div>
      <a href="http://www.sensiolabs.com/en/training/detail/SYMFONY-DOCTRINE-PARIS-JANUARY-2010">Jan 20</a>:
      Paris      <span style="font-size: 80%;">(1.3 + Doctrine - Français)</span>
    </div>
      <div>
      <a href="http://www.sensiolabs.com/en/training/detail/SYMFONY-DOCTRINE-PARIS-FEBRUARY-2010">Feb 24</a>:
      Paris      <span style="font-size: 80%;">(1.3 + Doctrine - Français)</span>
    </div>
      <div>
      <a href="http://www.sensiolabs.com/en/training/detail/SYMFONY-DOCTRINE-PARIS-MARCH-2010">Mar 24</a>:
      Paris      <span style="font-size: 80%;">(1.3 + Doctrine - Français)</span>
    </div>
  
  <div style="margin-top: 10px; text-align: right; font-size: 90%; font-weight: bold;"><a href="http://www.sensiolabs.com/en/training">and more...</a></div>
</div>

  <p>
  It's also available as a <a href="http://www.amazon.com/dp/1590597869?tag=symfonyprojec-20&amp;camp=14573&amp;creative=327641&amp;linkCode=as1&amp;creativeASIN=1590597869&amp;adid=0E8EEASTSYS05JS23TXC&amp;">printed copy</a>.
</p>
  <h2>Search</h2>
<form method="get" action="http://www.google.com/search">
  <input name="ie" value="UTF-8" type="hidden">
  <input name="oe" value="UTF-8" type="hidden">
  <input name="domains" value="www.symfony-project.org/book/1_0/en" type="hidden">
  <input name="sitesearch" value="www.symfony-project.org/book/1_0/en" type="hidden">
  <input name="q" size="12" maxlength="255" type="text">
  <input name="btnG" value="search" type="submit">
  <br><small>powered by google</small>
</form>
      </div>
    
      
<div id="documentation_release_info">
  You are currently browsing <em>"The Definitive Guide to symfony"</em> in <strong>English</strong> for the <strong>1.0</strong> version
  
      - Switch to version:

    <ul>
                              <li class="release_1_1">
          <a href="http://www.symfony-project.org/book/1_1/15-Unit-and-Functional-Testing">1.1</a>        </li>
                      <li class="release_1_2">
          <a href="http://www.symfony-project.org/book/1_2/15-Unit-and-Functional-Testing">1.2</a>        </li>
          </ul>
  
  
  
  </div>
  <div id="license">
        This work is licensed under a
        <a target="_blank" rel="license" href="http://www.gnu.org/licenses/fdl.txt">GFDL license</a>.
</div>
  
    <div id="content1" class="documentation culture_en">
      






<p>Automated tests are one of the greatest advances in programming
since object orientation. Particularly conducive to developing web
applications, they can guarantee the quality of an application even if
releases are numerous. Symfony provides a variety of tools for
facilitating automated testing, and these are introduced in this
chapter.</p>

<h2><a name="chapter_15_automated_tests">Automated Tests</a></h2>

<p>Any developer with experience developing web applications is well
aware of the time it takes to do testing well. Writing test cases,
running them, and analyzing the results is a tedious job. In addition,
the requirements of web applications tend to change constantly, which
leads to an ongoing stream of releases and a continuing need for code
refactoring. In this context, new errors are likely to regularly crop
up.</p>

<p>That's why automated tests are a suggested, if not required, part of
a successful development environment. A set of test cases can guarantee
that an application actually does what it is supposed to do. Even if
the internals are often reworked, the automated tests prevent
accidental regressions. Additionally, they compel developers to write
tests in a standardized, rigid format capable of being understood by a
testing framework.</p>

<p>Automated tests can sometimes replace developer documentation since
they can clearly illustrate what an application is supposed to do. A
good test suite shows what output should be expected for a set of test
inputs, and that is a good way to explain the purpose of a method.</p>

<p>The symfony framework applies this principle to itself. The
internals of the framework are validated by automated tests. These unit
and functional tests are not bundled with the standard symfony
distribution, but you can check them out from the SVN repository or
browse them online at <a href="http://trac.symfony-project.org/browser/branches/1.0/test">http://trac.symfony-project.org/browser/branches/1.0/test</a>.</p>

<h3><a name="chapter_15_sub_unit_and_functional_tests">Unit and Functional Tests</a></h3>

<p>Unit tests confirm that a unitary code component provides the
correct output for a given input. They validate how functions and
methods work in every particular case. Unit tests deal with one case at
a time, so for instance a single method may need several unit tests if
it works differently in certain situations.</p>

<p>Functional tests validate not a simple input-to-output conversion,
but a complete feature. For instance, a cache system can only be
validated by a functional test, because it involves more than one step:
The first time a page is requested, it is rendered; the second time, it
is taken from the cache. So functional tests validate a process and
require a scenario. In symfony, you should write functional tests for
all your actions.</p>

<p>For the most complex interactions, these two types may fall short.
Ajax interactions, for instance, require a web browser to execute
JavaScript, so automatically testing them requires a special
third-party tool. Furthermore, visual effects can only be validated by
a human.</p>

<p>If you have an extensive approach to automated testing, you will
probably need to use a combination of all these methods. As a
guideline, remember to keep tests simple and readable.</p>

<blockquote class="note"><p> Automated tests work by comparing a result
with an expected output. In other words, they evaluate assertions
(expressions like <code>$a == 2</code>). The value of an assertion is either <code>true</code> or <code>false</code>,
and it determines whether a test passes or fails. The word "assertion"
is commonly used when dealing with automated testing techniques.</p>
</blockquote>

<h3><a name="chapter_15_sub_test_driven_development">Test-Driven Development</a></h3>

<p>In the test-driven development (TDD) methodology, the tests are
written before the code. Writing tests first helps you to focus on the
tasks a function should accomplish before actually developing it. It's
a good practice that other methodologies, like Extreme Programming
(XP), recommend as well. Plus it takes into account the undeniable fact
that if you don't write unit tests first, you never write them.</p>

<p>For instance, imagine that you must develop a text-stripping
function. The function removes white spaces at the beginning and at the
end of the string, replaces nonalphabetical characters by underscores,
and transforms all uppercase characters to lowercase ones. In
test-driven development, you would first think about all the possible
cases and provide an example input and expected output for each, as
shown in Table 15-1.</p>

<p class="figure">Table 15-1 - A List of Test Cases for a Text-Stripping Function</p>

<table class="doc_table" cellspacing="0">
<thead>
<tr>
  <th>Input</th>
  <th>Expected Output</th>
</tr>
</thead>
<tbody>
<tr>
  <td><code>" foo "</code></td>
  <td><code>"foo"</code></td>
</tr>
<tr>
  <td><code>"foo bar"</code></td>
  <td><code>"foo_bar"</code></td>
</tr>
<tr>
  <td><code>"-)foo:..=bar?"</code></td>
  <td><code>"__foo____bar_"</code></td>
</tr>
<tr>
  <td><code>"FooBar"</code></td>
  <td><code>"foobar</code>"</td>
</tr>
<tr>
  <td><code>"Don't foo-bar me!"</code></td>
  <td><code>"don_t_foo_bar_me_"</code></td>
</tr>
</tbody>
</table>

<p>You would write the unit tests, run them, and see that they fail.
You would then add the necessary code to handle the first test case,
run the tests again, see that the first one passes, and go on like
that. Eventually, when all the test cases pass, the function is correct.</p>

<p>An application built with a test-driven methodology ends up with
roughly as much test code as actual code. As you don't want to spend
time debugging your tests cases, keep them simple.</p>

<blockquote class="note"><p> Refactoring a method can create new bugs
that didn't use to appear before. That's why it is also a good practice
to run all automated tests before deploying a new release of an
application in production--this is called regression testing.</p>
</blockquote>

<h3><a name="chapter_15_sub_the_lime_testing_framework">The Lime Testing Framework</a></h3>

<p>There are many unit test frameworks in the PHP world, with the most
well known being PhpUnit and SimpleTest. Symfony has its own, called
lime. It is based on the <code>Test::More</code> Perl library, and is
TAP compliant, which means that the result of tests is displayed as
specified in the Test Anything Protocol, designed for better
readability of test output.</p>

<p>Lime provides support for unit testing. It is more lightweight than other PHP testing frameworks and has several advantages:</p>

<ul>
<li>It launches test files in a sandbox to avoid strange side effects
between each test run. Not all testing frameworks guarantee a clean
environment for each test.</li>
<li>Lime tests are very readable, and so is the test output. On
compatible systems, lime uses color output in a smart way to
distinguish important information.</li>
<li>Symfony itself uses lime tests for regression testing, so many
examples of unit and functional tests can be found in the symfony
source code.</li>
<li>The lime core is validated by unit tests.</li>
<li>It is written in PHP, and it is fast and well coded. It is contained in a single file, <code>lime.php</code>, without any dependence.</li>
</ul>

<p>The various tests described next use the lime syntax. They work out of the box with any symfony installation.</p>

<blockquote class="note"><p> Unit and functional tests are not supposed
to be launched in production. They are developer tools, and as such,
they should be run in the developer's computer, not in the host server.</p>
</blockquote>

<h2><a name="chapter_15_unit_tests">Unit Tests</a></h2>

<p>Symfony unit tests are simple PHP files ending in <code>Test.php</code> and located in the <code>test/unit/</code> directory of your application. They follow a simple and readable syntax.</p>

<h3><a name="chapter_15_sub_what_do_unit_tests_look_like">What Do Unit Tests Look Like?</a></h3>

<p>Listing 15-1 shows a typical set of unit tests for the <code>strtolower()</code> function. It starts by an instantiation of the <code>lime_test</code> object (you don't need to worry about the parameters for now). Each unit test is a call to a method of the <code>lime_test</code> instance. The last parameter of these methods is always an optional string that serves as the output.</p>

<p class="figure">Listing 15-1 - Example Unit Test File, in <code>test/unit/strtolowerTest.php</code></p>

<pre class="php"><span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../bootstrap/unit.php'</span><span class="br0">)</span>;
<span class="kw1">require_once</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../../lib/strtolower.php'</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$t</span> = <span class="kw2">new</span> lime_test<span class="br0">(</span><span class="nu0">7</span>, <span class="kw2">new</span> lime_output_color<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// strtolower()</span>
<span class="re0">$t</span>-&gt;<span class="me1">diag</span><span class="br0">(</span><span class="st0">'strtolower()'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">isa_ok</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">'Foo'</span><span class="br0">)</span>, <span class="st0">'string'</span>,
    <span class="st0">'strtolower() returns a string'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">'FOO'</span><span class="br0">)</span>, <span class="st0">'foo'</span>,
    <span class="st0">'strtolower() transforms the input to lowercase'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">'foo'</span><span class="br0">)</span>, <span class="st0">'foo'</span>,
    <span class="st0">'strtolower() leaves lowercase characters unchanged'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">'12#?@~'</span><span class="br0">)</span>, <span class="st0">'12#?@~'</span>,
    <span class="st0">'strtolower() leaves non alphabetical characters unchanged'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">'FOO BAR'</span><span class="br0">)</span>, <span class="st0">'foo bar'</span>,
    <span class="st0">'strtolower() leaves blanks alone'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">'FoO bAr'</span><span class="br0">)</span>, <span class="st0">'foo bar'</span>,
    <span class="st0">'strtolower() deals with mixed case input'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">''</span><span class="br0">)</span>, <span class="st0">'foo'</span>,
    <span class="st0">'strtolower() transforms empty strings into foo'</span><span class="br0">)</span>;</pre>

<p>Launch the test set from the command line with the <code>test-unit</code>
task. The command-line output is very explicit, and it helps you
localize which tests failed and which passed. See the output of the
example test in Listing 15-2.</p>

<p class="figure">Listing 15-2 - Launching a Single Unit Test from the Command Line</p>

<pre class="command-line"><code>&gt; symfony test-unit strtolower

1..7
# strtolower()
ok 1 - strtolower() returns a string
ok 2 - strtolower() transforms the input to lowercase
ok 3 - strtolower() leaves lowercase characters unchanged
ok 4 - strtolower() leaves non alphabetical characters unchanged
ok 5 - strtolower() leaves blanks alone
ok 6 - strtolower() deals with mixed case input
not ok 7 - strtolower() transforms empty strings into foo
#     Failed test (.\batch\test.php at line 21)
#            got: ''
#       expected: 'foo'
# Looks like you failed 1 tests of 7.
</code></pre>

<blockquote class="tip"><p>
  The <code>include</code> statement at the
beginning of Listing 15-1 is optional, but it makes the test file an
independent PHP script that you can execute without the symfony command
line, by calling <code>php test/unit/strtolowerTest.php</code>.</p>
</blockquote>

<h3><a name="chapter_15_sub_unit_testing_methods">Unit Testing Methods</a></h3>

<p>The <code>lime_test</code> object comes with a large number of testing methods, as listed in Table 15-2.</p>

<p class="figure">Table 15-2 - Methods of the <code>lime_test</code> Object for Unit Testing</p>

<table class="doc_table" cellspacing="0">
<thead>
<tr>
  <th>Method</th>
  <th>Description</th>
</tr>
</thead>
<tbody>
<tr>
  <td><code>diag($msg)</code></td>
  <td>Outputs a comment but runs no test</td>
</tr>
<tr>
  <td><code>ok($test, $msg)</code></td>
  <td>Tests a condition and passes if it is true</td>
</tr>
<tr>
  <td><code>is($value1, $value2, $msg)</code></td>
  <td>Compares two values and passes if they are equal (<code>==</code>)</td>
</tr>
<tr>
  <td><code>isnt($value1, $value2, $msg)</code></td>
  <td>Compares two values and passes if they are not equal</td>
</tr>
<tr>
  <td><code>like($string, $regexp, $msg)</code></td>
  <td>Tests a string against a regular expression</td>
</tr>
<tr>
  <td><code>unlike($string, $regexp, $msg)</code></td>
  <td>Checks that a string doesn't match a regular expression</td>
</tr>
<tr>
  <td><code>cmp_ok($value1, $operator, $value2, $msg)</code></td>
  <td>Compares two arguments with an operator</td>
</tr>
<tr>
  <td><code>isa_ok($variable, $type, $msg)</code></td>
  <td>Checks the type of an argument</td>
</tr>
<tr>
  <td><code>isa_ok($object, $class, $msg)</code></td>
  <td>Checks the class of an object</td>
</tr>
<tr>
  <td><code>can_ok($object, $method, $msg)</code></td>
  <td>Checks the availability of a method for an object or a class</td>
</tr>
<tr>
  <td><code>is_deeply($array1, $array2, $msg)</code></td>
  <td>Checks that two arrays have the same values</td>
</tr>
<tr>
  <td><code>include_ok($file, $msg)</code></td>
  <td>Validates that a file exists and that it is properly included</td>
</tr>
<tr>
  <td><code>fail()</code></td>
  <td>Always fails--useful for testing exceptions</td>
</tr>
<tr>
  <td><code>pass()</code></td>
  <td>Always passes--useful for testing exceptions</td>
</tr>
<tr>
  <td><code>skip($msg, $nb_tests)</code></td>
  <td>Counts as <code>$nb_tests</code> tests--useful for conditional tests</td>
</tr>
<tr>
  <td><code>todo()</code></td>
  <td>Counts as a test--useful for tests yet to be written</td>
</tr>
</tbody>
</table>

<p>The syntax is quite straightforward; notice that most methods take a
message as their last parameter. This message is displayed in the
output when the test passes. Actually, the best way to learn these
methods is to test them, so have a look at Listing 15-3, which uses
them all.</p>

<p class="figure">Listing 15-3 - Testing Methods of the <code>lime_test</code> Object, in <code>test/unit/exampleTest.php</code></p>

<pre class="php"><span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../bootstrap/unit.php'</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Stub objects and functions for test purposes</span>
<span class="kw2">class</span> myObject
<span class="br0">{</span>
  <span class="kw2">public</span> <span class="kw2">function</span> myMethod<span class="br0">(</span><span class="br0">)</span>
  <span class="br0">{</span>
  <span class="br0">}</span>
<span class="br0">}</span>
&nbsp;
<span class="kw2">function</span> throw_an_exception<span class="br0">(</span><span class="br0">)</span>
<span class="br0">{</span>
  throw <span class="kw2">new</span> Exception<span class="br0">(</span><span class="st0">'exception thrown'</span><span class="br0">)</span>;
<span class="br0">}</span>
&nbsp;
<span class="co1">// Initialize the test object</span>
<span class="re0">$t</span> = <span class="kw2">new</span> lime_test<span class="br0">(</span><span class="nu0">16</span>, <span class="kw2">new</span> lime_output_color<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$t</span>-&gt;<span class="me1">diag</span><span class="br0">(</span><span class="st0">'hello world'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">ok</span><span class="br0">(</span><span class="nu0">1</span> == <span class="st0">'1'</span>, <span class="st0">'the equal operator ignores type'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="nu0">1</span>, <span class="st0">'1'</span>, <span class="st0">'a string is converted to a number for comparison'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">isnt</span><span class="br0">(</span><span class="nu0">0</span>, <span class="nu0">1</span>, <span class="st0">'zero and one are not equal'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">like</span><span class="br0">(</span><span class="st0">'test01'</span>, <span class="st0">'/test<span class="es0">\d</span>+/'</span>, <span class="st0">'test01 follows the test numbering pattern'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">unlike</span><span class="br0">(</span><span class="st0">'tests01'</span>, <span class="st0">'/test<span class="es0">\d</span>+/'</span>, <span class="st0">'tests01 does not follow the pattern'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">cmp_ok</span><span class="br0">(</span><span class="nu0">1</span>, <span class="st0">'&lt;'</span>, <span class="nu0">2</span>, <span class="st0">'one is inferior to two'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">cmp_ok</span><span class="br0">(</span><span class="nu0">1</span>, <span class="st0">'!=='</span>, <span class="kw2">true</span>, <span class="st0">'one and true are not identical'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">isa_ok</span><span class="br0">(</span><span class="st0">'foobar'</span>, <span class="st0">'string'</span>, <span class="st0">'<span class="es0">\'</span>foobar<span class="es0">\'</span> is a string'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">isa_ok</span><span class="br0">(</span><span class="kw2">new</span> myObject<span class="br0">(</span><span class="br0">)</span>, <span class="st0">'myObject'</span>, <span class="st0">'new creates object of the right class'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">can_ok</span><span class="br0">(</span><span class="kw2">new</span> myObject<span class="br0">(</span><span class="br0">)</span>, <span class="st0">'myMethod'</span>, <span class="st0">'objects of class myObject do have a myMethod method'</span><span class="br0">)</span>;
<span class="re0">$array1</span> = <span class="kw3">array</span><span class="br0">(</span><span class="nu0">1</span>, <span class="nu0">2</span>, <span class="kw3">array</span><span class="br0">(</span><span class="nu0">1</span> =&gt; <span class="st0">'foo'</span>, <span class="st0">'a'</span> =&gt; <span class="st0">'4'</span><span class="br0">)</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is_deeply</span><span class="br0">(</span><span class="re0">$array1</span>, <span class="kw3">array</span><span class="br0">(</span><span class="nu0">1</span>, <span class="nu0">2</span>, <span class="kw3">array</span><span class="br0">(</span><span class="nu0">1</span> =&gt; <span class="st0">'foo'</span>, <span class="st0">'a'</span> =&gt; <span class="st0">'4'</span><span class="br0">)</span><span class="br0">)</span>,
    <span class="st0">'the first and the second array are the same'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">include_ok</span><span class="br0">(</span><span class="st0">'./fooBar.php'</span>, <span class="st0">'the fooBar.php file was properly included'</span><span class="br0">)</span>;
&nbsp;
try
<span class="br0">{</span>
  throw_an_exception<span class="br0">(</span><span class="br0">)</span>;
  <span class="re0">$t</span>-&gt;<span class="me1">fail</span><span class="br0">(</span><span class="st0">'no code should be executed after throwing an exception'</span><span class="br0">)</span>;
<span class="br0">}</span>
catch <span class="br0">(</span>Exception <span class="re0">$e</span><span class="br0">)</span>
<span class="br0">{</span>
  <span class="re0">$t</span>-&gt;<span class="me1">pass</span><span class="br0">(</span><span class="st0">'exception caught successfully'</span><span class="br0">)</span>;
<span class="br0">}</span>
&nbsp;
<span class="kw1">if</span> <span class="br0">(</span>!<span class="kw3">isset</span><span class="br0">(</span><span class="re0">$foobar</span><span class="br0">)</span><span class="br0">)</span>
<span class="br0">{</span>
  <span class="re0">$t</span>-&gt;<span class="me1">skip</span><span class="br0">(</span><span class="st0">'skipping one test to keep the test count exact in the condition'</span>, <span class="nu0">1</span><span class="br0">)</span>;
<span class="br0">}</span>
<span class="kw1">else</span>
<span class="br0">{</span>
  <span class="re0">$t</span>-&gt;<span class="me1">ok</span><span class="br0">(</span><span class="re0">$foobar</span>, <span class="st0">'foobar'</span><span class="br0">)</span>;
<span class="br0">}</span>
&nbsp;
<span class="re0">$t</span>-&gt;<span class="me1">todo</span><span class="br0">(</span><span class="st0">'one test left to do'</span><span class="br0">)</span>;</pre>

<p>You will find a lot of other examples of the usage of these methods in the symfony unit tests.</p>

<blockquote class="tip"><p>
  You may wonder why you would use <code>is()</code> as opposed to <code>ok()</code> here. The error message output by <code>is()</code> is much more explicit; it shows both members of the test, while <code>ok()</code> just says that the condition failed.</p>
</blockquote>

<h3><a name="chapter_15_sub_testing_parameters">Testing Parameters</a></h3>

<p>The initialization of the <code>lime_test</code> object takes as its
first parameter the number of tests that should be executed. If the
number of tests finally executed differs from this number, the lime
output warns you about it. For instance, the test set of Listing 15-3
outputs as Listing 15-4. The initialization stipulated that 16 tests
were to run, but only 15 actually took place, so the output indicates
this.</p>

<p class="figure">Listing 15-4 - The Count of Test Run Helps You to Plan Tests</p>

<pre class="command-line"><code>&gt; symfony test-unit example

1..16
# hello world
ok 1 - the equal operator ignores type
ok 2 - a string is converted to a number for comparison
ok 3 - zero and one are not equal
ok 4 - test01 follows the test numbering pattern
ok 5 - tests01 does not follow the pattern
ok 6 - one is inferior to two
ok 7 - one and true are not identical
ok 8 - 'foobar' is a string
ok 9 - new creates object of the right class
ok 10 - objects of class myObject do have a myMethod method
ok 11 - the first and the second array are the same
not ok 12 - the fooBar.php file was properly included
#     Failed test (.\test\unit\testTest.php at line 27)
#       Tried to include './fooBar.php'
ok 13 - exception catched successfully
ok 14 # SKIP skipping one test to keep the test count exact in the condition
ok 15 # TODO one test left to do
# Looks like you planned 16 tests but only ran 15.
# Looks like you failed 1 tests of 16.
</code></pre>

<p>The <code>diag()</code> method doesn't count as a test. Use it to
show comments, so that your test output stays organized and legible. On
the other hand, the <code>todo()</code> and <code>skip()</code> methods count as actual tests. A <code>pass()</code>/<code>fail()</code> combination inside a <code>try</code>/<code>catch</code> block counts as a single test.</p>

<p>A well-planned test strategy must contain an expected number of
tests. You will find it very useful to validate your own test
files--especially in complex cases where tests are run inside
conditions or exceptions. And if the test fails at some point, you will
see it quickly because the final number of run tests won't match the
number given during initialization.</p>

<p>The second parameter of the constructor is an output object extending the <code>lime_output</code>
class. Most of the time, as tests are meant to be run through a CLI,
the output is a lime_output_color object, taking advantage of bash
coloring when available.</p>

<h3><a name="chapter_15_sub_the_test_unit_task">The test-unit Task</a></h3>

<p>The <code>test-unit</code> task, which launches unit tests from the
command line, expects either a list of test names or a file pattern.
See Listing 15-5 for details.</p>

<p class="figure">Listing 15-5 - Launching Unit Tests</p>

<pre><code>// Test directory structure
test/
  unit/
    myFunctionTest.php
    mySecondFunctionTest.php
    foo/
      barTest.php

&gt; symfony test-unit myFunction                   ## Run myFunctionTest.php
&gt; symfony test-unit myFunction mySecondFunction  ## Run both tests
&gt; symfony test-unit 'foo/*'                      ## Run barTest.php
&gt; symfony test-unit '*'                          ## Run all tests (recursive)
</code></pre>

<h3><a name="chapter_15_sub_stubs_fixtures_and_autoloading">Stubs, Fixtures, and Autoloading</a></h3>

<p>In a unit test, the autoloading feature is not active by default.
Each class that you use in a test must be either defined in the test
file or required as an external dependency. That's why many test files
start with a group of <code>include</code> lines, as Listing 15-6 demonstrates.</p>

<p class="figure">Listing 15-6 - Including Classes in Unit Tests</p>

<pre class="php"><span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../bootstrap/unit.php'</span><span class="br0">)</span>;
<span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../../config/config.php'</span><span class="br0">)</span>;
<span class="kw1">require_once</span><span class="br0">(</span><span class="re0">$sf_symfony_lib_dir</span>.<span class="st0">'/util/sfToolkit.class.php'</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$t</span> = <span class="kw2">new</span> lime_test<span class="br0">(</span><span class="nu0">7</span>, <span class="kw2">new</span> lime_output_color<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// isPathAbsolute()</span>
<span class="re0">$t</span>-&gt;<span class="me1">diag</span><span class="br0">(</span><span class="st0">'isPathAbsolute()'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span>sfToolkit::<span class="me2">isPathAbsolute</span><span class="br0">(</span><span class="st0">'/test'</span><span class="br0">)</span>, <span class="kw2">true</span>,
    <span class="st0">'isPathAbsolute() returns true if path is absolute'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span>sfToolkit::<span class="me2">isPathAbsolute</span><span class="br0">(</span><span class="st0">'<span class="es0">\\</span>test'</span><span class="br0">)</span>, <span class="kw2">true</span>,
    <span class="st0">'isPathAbsolute() returns true if path is absolute'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span>sfToolkit::<span class="me2">isPathAbsolute</span><span class="br0">(</span><span class="st0">'C:<span class="es0">\\</span>test'</span><span class="br0">)</span>, <span class="kw2">true</span>,
    <span class="st0">'isPathAbsolute() returns true if path is absolute'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span>sfToolkit::<span class="me2">isPathAbsolute</span><span class="br0">(</span><span class="st0">'d:/test'</span><span class="br0">)</span>, <span class="kw2">true</span>,
    <span class="st0">'isPathAbsolute() returns true if path is absolute'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span>sfToolkit::<span class="me2">isPathAbsolute</span><span class="br0">(</span><span class="st0">'test'</span><span class="br0">)</span>, <span class="kw2">false</span>,
    <span class="st0">'isPathAbsolute() returns false if path is relative'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span>sfToolkit::<span class="me2">isPathAbsolute</span><span class="br0">(</span><span class="st0">'../test'</span><span class="br0">)</span>, <span class="kw2">false</span>,
    <span class="st0">'isPathAbsolute() returns false if path is relative'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span>sfToolkit::<span class="me2">isPathAbsolute</span><span class="br0">(</span><span class="st0">'..<span class="es0">\\</span>test'</span><span class="br0">)</span>, <span class="kw2">false</span>,
    <span class="st0">'isPathAbsolute() returns false if path is relative'</span><span class="br0">)</span>;</pre>

<p>In unit tests, you need to instantiate not only the object you're
testing, but also the object it depends upon. Since unit tests must
remain unitary, depending on other classes may make more than one test
fail if one class is broken. In addition, setting up real objects can
be expensive, both in terms of lines of code and execution time. Keep
in mind that speed is crucial in unit testing because developers
quickly tire of a slow process.</p>

<p>Whenever you start including many scripts for a unit test, you may need a simple autoloading system. For this purpose, the <code>sfCore</code> class (which must be manually included) provides an <code>initSimpleAutoload()</code>
method, which expects an absolute path as parameter. All the classes
located under this path will be autoloaded. For instance, if you want
to have all the classes located under <code>$sf_symfony_lib_dir/util/</code> autoloaded, start your unit test script as follows:</p>

<pre class="php"><span class="kw1">require_once</span><span class="br0">(</span><span class="re0">$sf_symfony_lib_dir</span>.<span class="st0">'/util/sfCore.class.php'</span><span class="br0">)</span>;
sfCore::<span class="me2">initSimpleAutoload</span><span class="br0">(</span><span class="re0">$sf_symfony_lib_dir</span>.<span class="st0">'/util'</span><span class="br0">)</span>;</pre>

<blockquote class="tip"><p> The generated Propel objects rely on a long
cascade of classes, so as soon as you want to test a Propel object,
autoloading is necessary. Note that for Propel to work, you also need
to include the files under the <code>vendor/propel/</code> directory (so the call to <code>sfCore</code> becomes <code>sfCore::initSimpleAutoload(array(SF_ROOT_DIR.'/lib/model', $sf_symfony_lib_dir.'/vendor/propel'));</code>) and to add the Propel core to the include path (by calling <code>set_include_path($sf_symfony_lib_dir.'/vendor'.PATH_SEPARATOR.SF_ROOT_DIR.PATH_SEPARATOR.get_include_path()</code>.</p>
</blockquote>

<p>Another good workaround for the autoloading issues is the use of
stubs. A stub is an alternative implementation of a class where the
real methods are replaced with simple canned data. It mimics the
behavior of the real class, but without its cost. A good example of
stubs is a database connection or a web service interface. In Listing
15-7, the unit tests for a mapping API rely on a <code>WebService</code> class. Instead of calling the real <code>fetch()</code> method of the actual web service class, the test uses a stub that returns test data.</p>

<p class="figure">Listing 15-7 - Using Stubs in Unit Tests</p>

<pre class="php"><span class="kw1">require_once</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../../lib/WebService.class.php'</span><span class="br0">)</span>;
<span class="kw1">require_once</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../../lib/MapAPI.class.php'</span><span class="br0">)</span>;
&nbsp;
<span class="kw2">class</span> testWebService <span class="kw2">extends</span> WebService
<span class="br0">{</span>
  <span class="kw2">public</span> <span class="kw3">static</span> <span class="kw2">function</span> fetch<span class="br0">(</span><span class="br0">)</span>
  <span class="br0">{</span>
    <span class="kw1">return</span> <span class="kw3">file_get_contents</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/fixtures/data/fake_web_service.xml'</span><span class="br0">)</span>;
  <span class="br0">}</span>
<span class="br0">}</span>
&nbsp;
<span class="re0">$myMap</span> = <span class="kw2">new</span> MapAPI<span class="br0">(</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$t</span> = <span class="kw2">new</span> lime_test<span class="br0">(</span><span class="nu0">1</span>, <span class="kw2">new</span> lime_output_color<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$myMap</span>-&gt;<span class="me1">getMapSize</span><span class="br0">(</span>testWebService::<span class="me2">fetch</span><span class="br0">(</span><span class="br0">)</span>, <span class="nu0">100</span><span class="br0">)</span><span class="br0">)</span>;</pre>

<p>The test data can be more complex than a string or a call to a
method. Complex test data is often referred to as fixtures. For coding
clarity, it is often better to keep fixtures in separate files,
especially if they are used by more than one unit test file. Also,
don't forget that symfony can easily transform a YAML file into an
array with the <code>sfYAML::load()</code> method. This means that instead of writing long PHP arrays, you can write your test data in a YAML file, as in Listing 15-8.</p>

<p class="figure">Listing 15-8 - Using Fixture Files in Unit Tests</p>

<pre class="php"><span class="co1">// In fixtures.yml:</span>
-
  input:   <span class="st0">'/test'</span>
  output:  <span class="kw2">true</span>
  comment: isPathAbsolute<span class="br0">(</span><span class="br0">)</span> returns <span class="kw2">true</span> <span class="kw1">if</span> path is absolute
-
  input:   <span class="st0">'<span class="es0">\\</span>test'</span>
  output:  <span class="kw2">true</span>
  comment: isPathAbsolute<span class="br0">(</span><span class="br0">)</span> returns <span class="kw2">true</span> <span class="kw1">if</span> path is absolute
-
  input:   <span class="st0">'C:<span class="es0">\\</span>test'</span>
  output:  <span class="kw2">true</span>
  comment: isPathAbsolute<span class="br0">(</span><span class="br0">)</span> returns <span class="kw2">true</span> <span class="kw1">if</span> path is absolute
-
  input:   <span class="st0">'d:/test'</span>
  output:  <span class="kw2">true</span>
  comment: isPathAbsolute<span class="br0">(</span><span class="br0">)</span> returns <span class="kw2">true</span> <span class="kw1">if</span> path is absolute
-
  input:   <span class="st0">'test'</span>
  output:  <span class="kw2">false</span>
  comment: isPathAbsolute<span class="br0">(</span><span class="br0">)</span> returns <span class="kw2">false</span> <span class="kw1">if</span> path is relative
-
  input:   <span class="st0">'../test'</span>
  output:  <span class="kw2">false</span>
  comment: isPathAbsolute<span class="br0">(</span><span class="br0">)</span> returns <span class="kw2">false</span> <span class="kw1">if</span> path is relative
-
  input:   <span class="st0">'..<span class="es0">\\</span>test'</span>
  output:  <span class="kw2">false</span>
  comment: isPathAbsolute<span class="br0">(</span><span class="br0">)</span> returns <span class="kw2">false</span> <span class="kw1">if</span> path is relative
&nbsp;
<span class="co1">// In testTest.php</span>
<span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../bootstrap/unit.php'</span><span class="br0">)</span>;
<span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../../config/config.php'</span><span class="br0">)</span>;
<span class="kw1">require_once</span><span class="br0">(</span><span class="re0">$sf_symfony_lib_dir</span>.<span class="st0">'/util/sfToolkit.class.php'</span><span class="br0">)</span>;
<span class="kw1">require_once</span><span class="br0">(</span><span class="re0">$sf_symfony_lib_dir</span>.<span class="st0">'/util/sfYaml.class.php'</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$testCases</span> = sfYaml::<span class="me2">load</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/fixtures.yml'</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$t</span> = <span class="kw2">new</span> lime_test<span class="br0">(</span><span class="kw3">count</span><span class="br0">(</span><span class="re0">$testCases</span><span class="br0">)</span>, <span class="kw2">new</span> lime_output_color<span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// isPathAbsolute()</span>
<span class="re0">$t</span>-&gt;<span class="me1">diag</span><span class="br0">(</span><span class="st0">'isPathAbsolute()'</span><span class="br0">)</span>;
<span class="kw1">foreach</span> <span class="br0">(</span><span class="re0">$testCases</span> <span class="kw1">as</span> <span class="re0">$case</span><span class="br0">)</span>
<span class="br0">{</span>
  <span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span>sfToolkit::<span class="me2">isPathAbsolute</span><span class="br0">(</span><span class="re0">$case</span><span class="br0">[</span><span class="st0">'input'</span><span class="br0">]</span><span class="br0">)</span>, <span class="re0">$case</span><span class="br0">[</span><span class="st0">'output'</span><span class="br0">]</span>,<span class="re0">$case</span><span class="br0">[</span><span class="st0">'comment'</span><span class="br0">]</span><span class="br0">)</span>;
<span class="br0">}</span></pre>

<h2><a name="chapter_15_functional_tests">Functional Tests</a></h2>

<p>Functional tests validate parts of your applications. They simulate
a browsing session, make requests, and check elements in the response,
just like you would do manually to validate that an action does what
it's supposed to do. In functional tests, you run a scenario
corresponding to a use case.</p>

<h3><a name="chapter_15_sub_what_do_functional_tests_look_like">What Do Functional Tests Look Like?</a></h3>

<p>You could run your functional tests with a text browser and a lot of
regular expression assertions, but that would be a great waste of time.
Symfony provides a special object, called <code>sfBrowser</code>,
which acts like a browser connected to a symfony application without
actually needing a server--and without the slowdown of the HTTP
transport. It gives access to the core objects of each request (the
request, session, context, and response objects). Symfony also provides
an extension of this class called <code>sfTestBrowser</code>, designed especially for functional tests, which has all the abilities of the <code>sfBrowser</code> object plus some smart assert methods.</p>

<p>A functional test traditionally starts with an initialization of a
test browser object. This object makes a request to an action and
verifies that some elements are present in the response.</p>

<p>For example, every time you generate a module skeleton with the <code>init-module</code> or the <code>propel-init-crud</code>
tasks, symfony creates a simple functional test for this module. The
test makes a request to the default action of the module and checks the
response status code, the module and action calculated by the routing
system, and the presence of a certain sentence in the response content.
For a <code>foobar</code> module, the generated <code>foobarActionsTest.php</code> file looks like Listing 15-9.</p>

<p class="figure">Listing 15-9 - Default Functional Test for a New Module, in <code>tests/functional/frontend/foobarActionsTest.php</code></p>

<pre class="php"><span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../../bootstrap/functional.php'</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Create a new test browser</span>
<span class="re0">$browser</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$browser</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$browser</span>-&gt;
  <span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/index'</span><span class="br0">)</span>-&gt;
  <span class="me1">isStatusCode</span><span class="br0">(</span><span class="nu0">200</span><span class="br0">)</span>-&gt;
  <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'module'</span>, <span class="st0">'foobar'</span><span class="br0">)</span>-&gt;
  <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'action'</span>, <span class="st0">'index'</span><span class="br0">)</span>-&gt;
  <span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'body'</span>, <span class="st0">'!/This is a temporary page/'</span><span class="br0">)</span>
;</pre>

<blockquote class="tip"><p>
  The browser methods return an <code>sfTestBrowser</code>
object, so you can chain the method calls for more readability of your
test files. This is called a fluid interface to the object, because
nothing stops the flow of method calls.</p>
</blockquote>

<p>A functional test can contain several requests and more complex
assertions; you will soon discover all the possibilities in the
upcoming sections.</p>

<p>To launch a functional test, use the <code>test-functional</code> task with the symfony command line, as shown in Listing 15-10. This task expects an application name and a test name (omit the <code>Test.php</code> suffix).</p>

<p class="figure">Listing 15-10 - Launching a Single Functional Test from the Command Line</p>

<pre class="command-line"><code>&gt; symfony test-functional frontend foobarActions

# get /comment/index
ok 1 - status code is 200
ok 2 - request parameter module is foobar
ok 3 - request parameter action is index
not ok 4 - response selector body does not match regex /This is a temporary page/
# Looks like you failed 1 tests of 4.
1..4
</code></pre>

<p>The generated functional tests for a new module don't pass by default. This is because in a newly created module, the <code>index</code> action forwards to a congratulations page (included in the symfony <code>default</code> module), which contains the sentence "This is a temporary page". As long as you don't modify the <code>index</code> action, the tests for this module will fail, and this guarantees that you cannot pass all tests with an unfinished module.</p>

<blockquote class="note"><p>
  In functional tests, the autoloading is activated, so you don't have to include the files by hand.</p>
</blockquote>

<h3><a name="chapter_15_sub_browsing_with_the_sftestbrowser_object">Browsing with the sfTestBrowser Object</a></h3>

<p>The test browser is capable of making GET and POST requests. In both
cases, use a real URI as parameter. Listing 15-11 shows how to write
calls to the <code>sfTestBrowser</code> object to simulate requests.</p>

<p class="figure">Listing 15-11 - Simulating Requests with the <code>sfTestBrowser</code> Object</p>

<pre class="php"><span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../../bootstrap/functional.php'</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Create a new test browser</span>
<span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/show/id/1'</span><span class="br0">)</span>;                   <span class="co1">// GET request</span>
<span class="re0">$b</span>-&gt;<span class="me1">post</span><span class="br0">(</span><span class="st0">'/foobar/show'</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'id'</span> =&gt; <span class="nu0">1</span><span class="br0">)</span><span class="br0">)</span>;     <span class="co1">// POST request</span>
&nbsp;
<span class="co1">// The get() and post() methods are shortcuts to the call() method</span>
<span class="re0">$b</span>-&gt;<span class="me1">call</span><span class="br0">(</span><span class="st0">'/foobar/show/id/1'</span>, <span class="st0">'get'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">call</span><span class="br0">(</span><span class="st0">'/foobar/show'</span>, <span class="st0">'post'</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'id'</span> =&gt; <span class="nu0">1</span><span class="br0">)</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// The call() method can simulate requests with any method</span>
<span class="re0">$b</span>-&gt;<span class="me1">call</span><span class="br0">(</span><span class="st0">'/foobar/show/id/1'</span>, <span class="st0">'head'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">call</span><span class="br0">(</span><span class="st0">'/foobar/add/id/1'</span>, <span class="st0">'put'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">call</span><span class="br0">(</span><span class="st0">'/foobar/delete/id/1'</span>, <span class="st0">'delete'</span><span class="br0">)</span>;</pre>

<p>A typical browsing session contains not only requests to specific
actions, but also clicks on links and on browser buttons. As shown in
Listing 15-12, the <code>sfTestBrowser</code> object is also capable of simulating those.</p>

<p class="figure">Listing 15-12 - Simulating Navigation with the <code>sfTestBrowser</code> Object</p>

<pre class="php"><span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/'</span><span class="br0">)</span>;                  <span class="co1">// Request to the home page</span>
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/show/id/1'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">back</span><span class="br0">(</span><span class="br0">)</span>;                    <span class="co1">// Back to one page in history</span>
<span class="re0">$b</span>-&gt;<span class="me1">forward</span><span class="br0">(</span><span class="br0">)</span>;                 <span class="co1">// Forward one page in history</span>
<span class="re0">$b</span>-&gt;<span class="me1">reload</span><span class="br0">(</span><span class="br0">)</span>;                  <span class="co1">// Reload current page</span>
<span class="re0">$b</span>-&gt;<span class="me1">click</span><span class="br0">(</span><span class="st0">'go'</span><span class="br0">)</span>;               <span class="co1">// Look for a 'go' link or button and click it</span></pre>

<p>The test browser handles a stack of calls, so the <code>back()</code> and <code>forward()</code> methods work as they do on a real browser.</p>

<blockquote class="tip"><p>
  The test browser has its own mechanisms to manage sessions (<code>sfTestStorage</code>) and cookies.</p>
</blockquote>

<p>Among the interactions that most need to be tested, those associated
with forms probably rank first. To simulate form input and submission,
you have three choices. You can either make a POST request with the
parameters you wish to send, call <code>click()</code> with the form
parameters as an array, or fill in the fields one by one and click the
submit button. They all result in the same POST request anyhow. Listing
15-13 shows an example.</p>

<p class="figure">Listing 15-13 - Simulating Form Input with the <code>sfTestBrowser</code> Object</p>

<pre class="php"><span class="co1">// Example template in modules/foobar/templates/editSuccess.php</span>
<span class="kw2">&lt;?php</span> <span class="kw3">echo</span> form_tag<span class="br0">(</span><span class="st0">'foobar/update'</span><span class="br0">)</span> <span class="kw2">?&gt;</span>
  <span class="kw2">&lt;?php</span> <span class="kw3">echo</span> input_hidden_tag<span class="br0">(</span><span class="st0">'id'</span>, <span class="re0">$sf_params</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'id'</span><span class="br0">)</span><span class="br0">)</span> <span class="kw2">?&gt;</span>
  <span class="kw2">&lt;?php</span> <span class="kw3">echo</span> input_tag<span class="br0">(</span><span class="st0">'name'</span>, <span class="st0">'foo'</span><span class="br0">)</span> <span class="kw2">?&gt;</span>
  <span class="kw2">&lt;?php</span> <span class="kw3">echo</span> submit_tag<span class="br0">(</span><span class="st0">'go'</span><span class="br0">)</span> <span class="kw2">?&gt;</span>
  <span class="kw2">&lt;?php</span> <span class="kw3">echo</span> textarea<span class="br0">(</span><span class="st0">'text1'</span>, <span class="st0">'foo'</span><span class="br0">)</span> <span class="kw2">?&gt;</span>
  <span class="kw2">&lt;?php</span> <span class="kw3">echo</span> textarea<span class="br0">(</span><span class="st0">'text2'</span>, <span class="st0">'bar'</span><span class="br0">)</span> <span class="kw2">?&gt;</span>
&lt;/form&gt;
&nbsp;
<span class="co1">// Example functional test for this form</span>
<span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit/id/1'</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Option 1: POST request</span>
<span class="re0">$b</span>-&gt;<span class="me1">post</span><span class="br0">(</span><span class="st0">'/foobar/update'</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'id'</span> =&gt; <span class="nu0">1</span>, <span class="st0">'name'</span> =&gt; <span class="st0">'dummy'</span>, <span class="st0">'commit'</span> =&gt; <span class="st0">'go'</span><span class="br0">)</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Option 2: Click the submit button with parameters</span>
<span class="re0">$b</span>-&gt;<span class="me1">click</span><span class="br0">(</span><span class="st0">'go'</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'name'</span> =&gt; <span class="st0">'dummy'</span><span class="br0">)</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Option 3: Enter the form values field by field name then click the submit button</span>
<span class="re0">$b</span>-&gt;<span class="me1">setField</span><span class="br0">(</span><span class="st0">'name'</span>, <span class="st0">'dummy'</span><span class="br0">)</span>-&gt;
    <span class="me1">click</span><span class="br0">(</span><span class="st0">'go'</span><span class="br0">)</span>;</pre>

<blockquote class="note"><p> With the second and third options, the
default form values are automatically included in the form submission,
and the form target doesn't need to be specified.</p>
</blockquote>

<p>When an action finishes by a <code>redirect()</code>, the test browser doesn't automatically follow the redirection; you must follow it manually with <code>followRedirect()</code>, as demonstrated in Listing 15-14.</p>

<p class="figure">Listing 15-14 - The Test Browser Doesn't Automatically Follow Redirects</p>

<pre class="php"><span class="co1">// Example action in modules/foobar/actions/actions.class.php</span>
<span class="kw2">public</span> <span class="kw2">function</span> executeUpdate<span class="br0">(</span><span class="br0">)</span>
<span class="br0">{</span>
  ...
  <span class="re0">$this</span>-&gt;<span class="me1">redirect</span><span class="br0">(</span><span class="st0">'foobar/show?id='</span>.<span class="re0">$this</span>-&gt;<span class="me1">getRequestParameter</span><span class="br0">(</span><span class="st0">'id'</span><span class="br0">)</span><span class="br0">)</span>;
<span class="br0">}</span>
&nbsp;
<span class="co1">// Example functional test for this action</span>
<span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;   
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit?id=1'</span><span class="br0">)</span>-&gt;
    <span class="me1">click</span><span class="br0">(</span><span class="st0">'go'</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'name'</span> =&gt; <span class="st0">'dummy'</span><span class="br0">)</span><span class="br0">)</span>-&gt;
    <span class="me1">isRedirected</span><span class="br0">(</span><span class="br0">)</span>-&gt;   <span class="co1">// Check that request is redirected</span>
    followRedirect<span class="br0">(</span><span class="br0">)</span>;    <span class="co1">// Manually follow the redirection</span></pre>

<p>There is one last method you should know about that is useful for browsing: <code>restart()</code> reinitializes the browsing history, session, and cookies--as if you restarted your browser.</p>

<p>Once it has made a first request, the <code>sfTestBrowser</code>
object can give access to the request, context, and response objects.
It means that you can check a lot of things, ranging from the text
content to the response headers, the request parameters, and
configuration:</p>

<pre class="php"><span class="re0">$request</span>  = <span class="re0">$b</span>-&gt;<span class="me1">getRequest</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$context</span>  = <span class="re0">$b</span>-&gt;<span class="me1">getContext</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$response</span> = <span class="re0">$b</span>-&gt;<span class="me1">getResponse</span><span class="br0">(</span><span class="br0">)</span>;</pre>

<blockquote class="sidebar"><p class="title">The sfBrowser object</p>
  
  <p>All the browsing methods described in Listings 15-10 to 15-13 are also available out of the testing scope, throughout the <code>sfBrowser</code> object. You can call it as follows:</p>

<pre class="php"><span class="co1">// Create a new browser</span>
<span class="re0">$b</span> = <span class="kw2">new</span> sfBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/show/id/1'</span><span class="br0">)</span>-&gt;
    <span class="me1">setField</span><span class="br0">(</span><span class="st0">'name'</span>, <span class="st0">'dummy'</span><span class="br0">)</span>-&gt;
    <span class="me1">click</span><span class="br0">(</span><span class="st0">'go'</span><span class="br0">)</span>;
<span class="re0">$content</span> = <span class="re0">$b</span>-&gt;<span class="me1">getResponse</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">getContent</span><span class="br0">(</span><span class="br0">)</span>;
...</pre>
  
  <p>The <code>sfBrowser</code>
object is a very useful tool for batch scripts, for instance, if you
want to browse a list of pages to generate a cached version for each
(refer to Chapter 18 for a detailed example).</p>
</blockquote>

<h3><a name="chapter_15_sub_using_assertions">Using Assertions</a></h3>

<p>Due to the <code>sfTestBrowser</code> object having access to the
response and other components of the request, you can do tests on these
components. You could create a new <code>lime_test</code> object for that purpose, but fortunately <code>sfTestBrowser</code> proposes a <code>test()</code> method that returns a <code>lime_test</code> object where you can call the unit assertion methods described previously. Check Listing 15-15 to see how to do assertions via <code>sfTestBrowser</code>.</p>

<p class="figure">Listing 15-15 - The Test Browser Provides Testing Abilities with the <code>test()</code> Method</p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit/id/1'</span><span class="br0">)</span>;
<span class="re0">$request</span>  = <span class="re0">$b</span>-&gt;<span class="me1">getRequest</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$context</span>  = <span class="re0">$b</span>-&gt;<span class="me1">getContext</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$response</span> = <span class="re0">$b</span>-&gt;<span class="me1">getResponse</span><span class="br0">(</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Get access to the lime_test methods via the test() method</span>
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$request</span>-&gt;<span class="me1">getParameter</span><span class="br0">(</span><span class="st0">'id'</span><span class="br0">)</span>, <span class="nu0">1</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$response</span>-&gt;<span class="me1">getStatuscode</span><span class="br0">(</span><span class="br0">)</span>, <span class="nu0">200</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$response</span>-&gt;<span class="me1">getHttpHeader</span><span class="br0">(</span><span class="st0">'content-type'</span><span class="br0">)</span>, <span class="st0">'text/html;charset=utf-8'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">like</span><span class="br0">(</span><span class="re0">$response</span>-&gt;<span class="me1">getContent</span><span class="br0">(</span><span class="br0">)</span>, <span class="st0">'/edit/'</span><span class="br0">)</span>;</pre>

<blockquote class="note"><p>
  The <code>getResponse()</code>, <code>getContext()</code>, <code>getRequest()</code>, and <code>test()</code> methods don't return an <code>sfTestBrowser</code> object, therefore you can't chain other <code>sfTestBrowser</code> method calls after them.</p>
</blockquote>

<p>You can check incoming and outgoing cookies easily via the request and response objects, as shown in Listing 15-16.</p>

<p class="figure">Listing 15-16 - Testing Cookies with <code>sfTestBrowser</code></p>

<pre class="php"><span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$request</span>-&gt;<span class="me1">getCookie</span><span class="br0">(</span><span class="st0">'foo'</span><span class="br0">)</span>, <span class="st0">'bar'</span><span class="br0">)</span>;     <span class="co1">// Incoming cookie</span>
<span class="re0">$cookies</span> = <span class="re0">$response</span>-&gt;<span class="me1">getCookies</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$cookies</span><span class="br0">[</span><span class="st0">'foo'</span><span class="br0">]</span>, <span class="st0">'foo=bar'</span><span class="br0">)</span>;            <span class="co1">// Outgoing cookie</span></pre>

<p>Using the <code>test()</code> method to test the request elements ends up in long lines. Fortunately, <code>sfTestbrowser</code> contains a bunch of proxy methods that help you keep your functional tests readable and short--in addition to returning an <code>sfTestBrowser</code> object themselves. For instance, you can rewrite Listing 15-15 in a faster way, as shown in Listing 15-17.</p>

<p class="figure">Listing 15-17 - Testing Directly with <code>sfTestBrowser</code></p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit/id/1'</span><span class="br0">)</span>-&gt;
    <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'id'</span>, <span class="nu0">1</span><span class="br0">)</span>-&gt;
    <span class="me1">isStatusCode</span><span class="br0">(</span><span class="br0">)</span>-&gt;
    <span class="me1">isResponseHeader</span><span class="br0">(</span><span class="st0">'content-type'</span>, <span class="st0">'text/html; charset=utf-8'</span><span class="br0">)</span>-&gt;
    <span class="me1">responseContains</span><span class="br0">(</span><span class="st0">'edit'</span><span class="br0">)</span>;</pre>

<p>The status 200 is the default value of the parameter expected by <code>isStatusCode()</code>, so you can call it without any argument to test a successful response.</p>

<p>One more advantage of proxy methods is that you don't need to specify an output text as you would with a <code>lime_test</code> method. The messages are generated automatically by the proxy methods, and the test output is clear and readable.</p>

<pre><code># get /foobar/edit/id/1
ok 1 - request parameter "id" is "1"
ok 2 - status code is "200"
ok 3 - response header "content-type" is "text/html"
ok 4 - response contains "edit"
1..4
</code></pre>

<p>In practice, the proxy methods of Listing 15-17 cover most of the usual tests, so you will seldom use the <code>test()</code> method on an <code>sfTestBrowser</code> object.</p>

<p>Listing 15-14 showed that <code>sfTestBrowser</code> doesn't
automatically follow redirections. This has one advantage: You can test
a redirection. For instance, Listing 15-18 shows how to test the
response of Listing 15-14.</p>

<p class="figure">Listing 15-18 - Testing Redirections with <code>sfTestBrowser</code></p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;
    <span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit/id/1'</span><span class="br0">)</span>-&gt;
    <span class="me1">click</span><span class="br0">(</span><span class="st0">'go'</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'name'</span> =&gt; <span class="st0">'dummy'</span><span class="br0">)</span><span class="br0">)</span>-&gt;
    <span class="me1">isStatusCode</span><span class="br0">(</span><span class="nu0">200</span><span class="br0">)</span>-&gt;
    <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'module'</span>, <span class="st0">'foobar'</span><span class="br0">)</span>-&gt;
    <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'action'</span>, <span class="st0">'update'</span><span class="br0">)</span>-&gt;
&nbsp;
    <span class="me1">isRedirected</span><span class="br0">(</span><span class="br0">)</span>-&gt;      <span class="co1">// Check that the response is a redirect</span>
    followRedirect<span class="br0">(</span><span class="br0">)</span>-&gt;    <span class="co1">// Manually follow the redirection</span>
&nbsp;
    isStatusCode<span class="br0">(</span><span class="nu0">200</span><span class="br0">)</span>-&gt;
    <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'module'</span>, <span class="st0">'foobar'</span><span class="br0">)</span>-&gt;
    <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'action'</span>, <span class="st0">'show'</span><span class="br0">)</span>;</pre>

<h3><a name="chapter_15_sub_using_css_selectors">Using CSS Selectors</a></h3>

<p>Many of the functional tests validate that a page is correct by
checking for the presence of text in the content. With the help of
regular expressions in the <code>responseContains()</code> method, you
can check displayed text, a tag's attributes, or values. But as soon as
you want to check something deeply buried in the response DOM, regular
expressions are not ideal.</p>

<p>That's why the <code>sfTestBrowser</code> object supports a <code>getResponseDom()</code>
method. It returns a libXML2 DOM object, much easier to parse and test
than a flat text. Refer to Listing 15-19 for an example of using this
method.</p>

<p class="figure">Listing 15-19 - The Test Browser Gives Access to the Response Content As a DOM Object</p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit/id/1'</span><span class="br0">)</span>;
<span class="re0">$dom</span> = <span class="re0">$b</span>-&gt;<span class="me1">getResponseDom</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$dom</span>-&gt;<span class="me1">getElementsByTagName</span><span class="br0">(</span><span class="st0">'input'</span><span class="br0">)</span>-&gt;<span class="me1">item</span><span class="br0">(</span><span class="nu0">1</span><span class="br0">)</span>-&gt;<span class="me1">getAttribute</span><span class="br0">(</span><span class="st0">'type'</span><span class="br0">)</span>,<span class="st0">'text'</span><span class="br0">)</span>;</pre>

<p>But parsing an HTML document with the PHP DOM methods is still not
fast and easy enough. If you are familiar with the CSS selectors, you
know that they are an even more powerful way to retrieve elements from
an HTML document. Symfony provides a tool class called <code>sfDomCssSelector</code>
that expects a DOM document as construction parameter. It has a
getTexts() method that returns an array of strings according to a CSS
selector, and a getElements() method that returns an array of DOM
elements. See an example in Listing 15-20.</p>

<p class="figure">Listing 15-20 - The Test Browser Gives Access to the Response Content As an <code>sfDomCssSelector</code> Object</p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit/id/1'</span><span class="br0">)</span>;
<span class="re0">$c</span> = <span class="kw2">new</span> sfDomCssSelector<span class="br0">(</span><span class="re0">$b</span>-&gt;<span class="me1">getResponseDom</span><span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$c</span>-&gt;<span class="me1">getTexts</span><span class="br0">(</span><span class="st0">'form input[type="hidden"][value="1"]'</span><span class="br0">)</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">''</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$c</span>-&gt;<span class="me1">getTexts</span><span class="br0">(</span><span class="st0">'form textarea[name="text1"]'</span><span class="br0">)</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'foo'</span><span class="br0">)</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">test</span><span class="br0">(</span><span class="br0">)</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="re0">$c</span>-&gt;<span class="me1">getTexts</span><span class="br0">(</span><span class="st0">'form input[type="submit"]'</span><span class="br0">)</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">''</span><span class="br0">)</span><span class="br0">)</span>;</pre>

<p>In its constant pursuit for brevity and clarity, symfony provides a shortcut for this: the <code>checkResponseElement()</code> proxy method. This method makes Listing 15-20 look like Listing 15-21.</p>

<p class="figure">Listing 15-21 - The Test Browser Gives Access to the Elements of the Response by CSS Selectors</p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit/id/1'</span><span class="br0">)</span>-&gt;
    <span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'form input[type="hidden"][value="1"]'</span>, <span class="kw2">true</span><span class="br0">)</span>-&gt;
    <span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'form textarea[name="text1"]'</span>, <span class="st0">'foo'</span><span class="br0">)</span>-&gt;
    <span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'form input[type="submit"]'</span>, <span class="nu0">1</span><span class="br0">)</span>;</pre>

<p>The behavior of the <code>checkResponseElement()</code> method depends on the type of the second argument that it receives:</p>

<ul>
<li>If it is a Boolean, it checks that an element matching the CSS selector exists.</li>
<li>If it is an integer, it checks that the CSS selector returns this number of results.</li>
<li>If it is a regular expression, it checks that the first element found by the CSS selector matches it.</li>
<li>If it is a regular expression preceded by <code>!</code>, it checks that the first element doesn't match the pattern.</li>
<li>For other cases, it compares the first element found by the CSS selector with the second argument as a string.</li>
</ul>

<p>The method accepts a third optional parameter, in the shape of an
associative array. It allows you to have the test performed not on the
first element returned by the selector (if it returns several), but on
another element at a certain position, as shown in Listing 15-22.</p>

<p class="figure">Listing 15-22 - Using the Position Option to Match an Element at a Certain Position</p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit?id=1'</span><span class="br0">)</span>-&gt;
    <span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'form textarea'</span>, <span class="st0">'foo'</span><span class="br0">)</span>-&gt;
    <span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'form textarea'</span>, <span class="st0">'bar'</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'position'</span> =&gt; <span class="nu0">1</span><span class="br0">)</span><span class="br0">)</span>;</pre>

<p>The options array can also be used to perform two tests at the same
time. You can test that there is an element matching a selector and how
many there are, as demonstrated in Listing 15-23.</p>

<p class="figure">Listing 15-23 - Using the Count Option to Count the Number of Matches</p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/edit?id=1'</span><span class="br0">)</span>-&gt;
    <span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'form input'</span>, <span class="kw2">true</span>, <span class="kw3">array</span><span class="br0">(</span><span class="st0">'count'</span> =&gt; <span class="nu0">3</span><span class="br0">)</span><span class="br0">)</span>;</pre>

<p>The selector tool is very powerful. It accepts most of the CSS 2.1
selectors, and you can use it for complex queries such as those of
Listing 15-24.</p>

<p class="figure">Listing 15-24 - Example of Complex CSS Selectors Accepted by <code>checkResponseElement()</code></p>

<pre class="php"><span class="re0">$b</span>-&gt;<span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'ul#list li a[href]'</span>, <span class="st0">'click me'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'ul &gt; li'</span>, <span class="st0">'click me'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'ul + li'</span>, <span class="st0">'click me'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'h1, h2'</span>, <span class="st0">'click me'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'a[class$="foo"][href*="bar.html"]'</span>, <span class="st0">'my link'</span><span class="br0">)</span>;</pre>

<h3><a name="chapter_15_sub_working_in_the_test_environment">Working in the Test Environment</a></h3>

<p>The <code>sfTestBrowser</code> object uses a special front controller, set to the <code>test</code> environment. The default configuration for this environment appears in Listing 15-25.</p>

<p class="figure">Listing 15-25 - Default Test Environment Configuration, in <code>myapp/config/settings.yml</code></p>

<pre><code>test:
  .settings:
    # E_ALL | E_STRICT &amp; ~E_NOTICE = 2047
    error_reporting:        2047
    cache:                  off
    web_debug:              off
    no_script_name:         off
    etag:                   off
</code></pre>

<p>The cache and the web debug toolbar are set to <code>off</code> in this environment. However, the code execution still leaves traces in a log file, distinct from the <code>dev</code> and <code>prod</code> log files, so that you can check it independently (<code>myproject/log/myapp_test.log</code>).
In this environment, the exceptions don't stop the execution of the
scripts--so that you can run an entire set of tests even if one fails.
You can have specific database connection settings, for instance, to
use another database with test data in it.</p>

<p>Before using the <code>sfTestBrowser</code> object, you have to
initialize it. If you need to, you can specify a hostname for the
application and an IP address for the client--that is, if your
application makes controls over these two parameters. Listing 15-26
demonstrates how to do this.</p>

<p class="figure">Listing 15-26 - Setting Up the Test Browser with Hostname and IP</p>

<pre class="php"><span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="st0">'myapp.example.com'</span>, <span class="st0">'123.456.789.123'</span><span class="br0">)</span>;</pre>

<h3><a name="chapter_15_sub_the_test_functional_task">The test-functional Task</a></h3>

<p>The <code>test-functional</code> task can run one or more functional
tests, depending on the number of arguments received. The rules look
much like the ones of the <code>test-unit</code> task, except that the functional test task always expects an application as first argument, as shown in Listing 15-27.</p>

<p class="figure">Listing 15-27 - Functional Test Task Syntax</p>

<pre><code>// Test directory structure
test/
  functional/
    frontend/
      myModuleActionsTest.php
      myScenarioTest.php
    backend/
      myOtherScenarioTest.php

## Run all functional tests for one application, recursively
&gt; symfony test-functional frontend

## Run one given functional test
&gt; symfony test-functional frontend myScenario

## Run several tests based on a pattern
&gt; symfony test-functional frontend my*
</code></pre>

<h2><a name="chapter_15_test_naming_practices">Test Naming Practices</a></h2>

<p>This section lists a few good practices to keep your tests organized
and easy to maintain. The tips concern file organization, unit tests,
and functional tests.</p>

<p>As for the file structure, you should name the unit test files using
the class they are supposed to test, and name the functional test files
using the module or the scenario they are supposed to test. See Listing
15-28 for an example. Your <code>test/</code> directory will soon
contain a lot of files, and finding a test might prove difficult in the
long run if you don't follow these guidelines.</p>

<p class="figure">Listing 15-28 - Example File Naming Practice</p>

<pre><code>test/
  unit/
    myFunctionTest.php
    mySecondFunctionTest.php
    foo/
      barTest.php
  functional/
    frontend/
      myModuleActionsTest.php
      myScenarioTest.php
    backend/
      myOtherScenarioTest.php
</code></pre>

<p>For unit tests, a good practice is to group the tests by function or method, and start each test group with a <code>diag()</code>
call. The messages of each unit test should contain the name of the
function or method tested, followed by a verb and a property, so that
the test output looks like a sentence describing a property of the
object. Listing 15-29 shows an example.</p>

<p class="figure">Listing 15-29 - Example Unit Test Naming Practice</p>

<pre class="php"><span class="co1">// strtolower()</span>
<span class="re0">$t</span>-&gt;<span class="me1">diag</span><span class="br0">(</span><span class="st0">'strtolower()'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">isa_ok</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">'Foo'</span><span class="br0">)</span>, <span class="st0">'string'</span>, <span class="st0">'strtolower() returns a string'</span><span class="br0">)</span>;
<span class="re0">$t</span>-&gt;<span class="me1">is</span><span class="br0">(</span><span class="kw3">strtolower</span><span class="br0">(</span><span class="st0">'FOO'</span><span class="br0">)</span>, <span class="st0">'foo'</span>, <span class="st0">'strtolower() transforms the input to lowercase'</span><span class="br0">)</span>;
&nbsp;
<span class="co2"># strtolower()</span>
ok <span class="nu0">1</span> - <span class="kw3">strtolower</span><span class="br0">(</span><span class="br0">)</span> returns a string
ok <span class="nu0">2</span> - <span class="kw3">strtolower</span><span class="br0">(</span><span class="br0">)</span> transforms the input to lowercase</pre>

<p>Functional tests should be grouped by page and start with a request. Listing 15-30 illustrates this practice.</p>

<p class="figure">Listing 15-30 - Example Functional Test Naming Practice</p>

<pre class="php"><span class="re0">$browser</span>-&gt;
  <span class="me1">get</span><span class="br0">(</span><span class="st0">'/foobar/index'</span><span class="br0">)</span>-&gt;
  <span class="me1">isStatusCode</span><span class="br0">(</span><span class="nu0">200</span><span class="br0">)</span>-&gt;
  <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'module'</span>, <span class="st0">'foobar'</span><span class="br0">)</span>-&gt;
  <span class="me1">isRequestParameter</span><span class="br0">(</span><span class="st0">'action'</span>, <span class="st0">'index'</span><span class="br0">)</span>-&gt;
  <span class="me1">checkResponseElement</span><span class="br0">(</span><span class="st0">'body'</span>, <span class="st0">'/foobar/'</span><span class="br0">)</span>
;
&nbsp;
<span class="co2"># get /comment/index</span>
ok <span class="nu0">1</span> - status code is <span class="nu0">200</span>
ok <span class="nu0">2</span> - request parameter module is foobar
ok <span class="nu0">3</span> - request parameter action is index
ok <span class="nu0">4</span> - response selector body matches regex /foobar/</pre>

<p>If you follow this convention, the output of your test will be clean
enough to use as a developer documentation of your project--enough so
in some cases to make actual documentation useless.</p>

<h2><a name="chapter_15_special_testing_needs">Special Testing Needs</a></h2>

<p>The unit and functional test tools provided by symfony should
suffice in most cases. A few additional techniques are listed here to
resolve common problems in automated testing: launching tests in an
isolated environment, accessing a database within tests, testing the
cache, and testing interactions on the client side.</p>

<h3><a name="chapter_15_sub_executing_tests_in_a_test_harness">Executing Tests in a Test Harness</a></h3>

<p>The <code>test-unit</code> and <code>test-functional</code> tasks
can launch a single test or a set of tests. But if you call these tasks
without any parameter, they launch all the unit and functional tests
written in the <code>test/</code> directory. A particular mechanism is
involved to isolate each test file in an independent sandbox, to avoid
contamination risks between tests. Furthermore, as it wouldn't make
sense to keep the same output as with single test files in that case
(the output would be thousands of lines long), the tests results are
compacted into a synthetic view. That's why the execution of a large
number of test files uses a test harness, that is, an automated test
framework with special abilities. A test harness relies on a component
of the lime framework called <code>lime_harness</code>. It shows a
test status file by file, and an overview at the end of the number of
tests passed over the total, as you see in Listing 15-31.</p>

<p class="figure">Listing 15-31 - Launching All Tests in a Test Harness</p>

<pre class="command-line"><code>&gt; symfony test-all

unit/myFunctionTest.php................ok
unit/mySecondFunctionTest.php..........ok
unit/foo/barTest.php...................not ok

Failed Test                     Stat  Total   Fail  List of Failed
------------------------------------------------------------------
unit/foo/barTest.php               0      2      2  62 63
Failed 1/3 test scripts, 66.66% okay. 2/53 subtests failed, 96.22% okay.
</code></pre>

<p>The tests are executed the same way as when you call them one by
one, only the output is made shorter to be really useful. In
particular, the final chart focuses on the failed tests and helps you
locate them.</p>

<p>You can launch all the tests with one call using the <code>test-all</code>
task, which also uses a test harness, as shown in Listing 15-32. This
is something that you should do before every transfer to production, to
ensure that no regression has appeared since the latest release.</p>

<p class="figure">Listing 15-32 - Launching All the Tests of a Project</p>

<pre class="command-line"><code>&gt; symfony test-all
</code></pre>

<h3><a name="chapter_15_sub_accessing_a_database">Accessing a Database</a></h3>

<p>Unit tests often need to access a database. A database connection is automatically initialized when you call <code>sfTestBrowser::get()</code> for the first time. However, if you want to access the database even before using <code>sfTestBrowser</code>, you have to initialize a <code>sfDabataseManager</code> object manually, as in Listing 15-33.</p>

<p class="figure">Listing 15-33 - Initializing a Database in a Test</p>

<pre class="php"><span class="re0">$databaseManager</span> = <span class="kw2">new</span> sfDatabaseManager<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$databaseManager</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Optionally, you can retrieve the current database connection</span>
<span class="re0">$con</span> = Propel::<span class="me2">getConnection</span><span class="br0">(</span><span class="br0">)</span>;</pre>

<p>You should populate the database with fixtures before starting the tests. This can be done via the <code>sfPropelData</code> object. This object can load data from a file, just like the <code>propel-load-data</code> task, or from an array, as shown in Listing 15-34.</p>

<p class="figure">Listing 15-34 - Populating a Database from a Test File</p>

<pre class="php"><span class="re0">$data</span> = <span class="kw2">new</span> sfPropelData<span class="br0">(</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Loading data from file</span>
<span class="re0">$data</span>-&gt;<span class="me1">loadData</span><span class="br0">(</span>sfConfig::<span class="me2">get</span><span class="br0">(</span><span class="st0">'sf_data_dir'</span><span class="br0">)</span>.<span class="st0">'/fixtures/test_data.yml'</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Loading data from array</span>
<span class="re0">$fixtures</span> = <span class="kw3">array</span><span class="br0">(</span>
  <span class="st0">'Article'</span> =&gt; <span class="kw3">array</span><span class="br0">(</span>
    <span class="st0">'article_1'</span> =&gt; <span class="kw3">array</span><span class="br0">(</span>
      <span class="st0">'title'</span>      =&gt; <span class="st0">'foo title'</span>,
      <span class="st0">'body'</span>       =&gt; <span class="st0">'bar body'</span>,
      <span class="st0">'created_at'</span> =&gt; <span class="kw3">time</span><span class="br0">(</span><span class="br0">)</span>,
    <span class="br0">)</span>,
    <span class="st0">'article_2'</span>    =&gt; <span class="kw3">array</span><span class="br0">(</span>
      <span class="st0">'title'</span>      =&gt; <span class="st0">'foo foo title'</span>,
      <span class="st0">'body'</span>       =&gt; <span class="st0">'bar bar body'</span>,
      <span class="st0">'created_at'</span> =&gt; <span class="kw3">time</span><span class="br0">(</span><span class="br0">)</span>,
    <span class="br0">)</span>,
  <span class="br0">)</span>,
<span class="br0">)</span>;
<span class="re0">$data</span>-&gt;<span class="me1">loadDataFromArray</span><span class="br0">(</span><span class="re0">$fixtures</span><span class="br0">)</span>;</pre>

<p>Then, use the Propel objects as you would in a normal application,
according to your testing needs. Remember to include their files in
unit tests (you can use the sfCore::initSimpleAutoload() method to
automate it, as explained in a tip in the "Stubs, Fixtures, and
Autoloading" section previously in this chapter). Propel objects are
autoloaded in functional tests.</p>

<h3><a name="chapter_15_sub_testing_the_cache">Testing the Cache</a></h3>

<p>When you enable caching for an application, the functional tests should verify that the cached actions do work as expected.</p>

<p>The first thing to do is enable cache for the test environment (in the <code>settings.yml</code> file). Then, if you want to test whether a page comes from the cache or whether it is generated, you should use the <code>isCached()</code> test method provided by the <code>sfTestBrowser</code> object. Listing 15-35 demonstrates this method.</p>

<p class="figure">Listing 15-35 - Testing the Cache with the <code>isCached()</code> Method</p>

<pre class="php"><span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">(</span><span class="kw3">dirname</span><span class="br0">(</span><span class="kw2">__FILE__</span><span class="br0">)</span>.<span class="st0">'/../../bootstrap/functional.php'</span><span class="br0">)</span>;
&nbsp;
<span class="co1">// Create a new test browser</span>
<span class="re0">$b</span> = <span class="kw2">new</span> sfTestBrowser<span class="br0">(</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">initialize</span><span class="br0">(</span><span class="br0">)</span>;
&nbsp;
<span class="re0">$b</span>-&gt;<span class="me1">get</span><span class="br0">(</span><span class="st0">'/mymodule'</span><span class="br0">)</span>;
<span class="re0">$b</span>-&gt;<span class="me1">isCached</span><span class="br0">(</span><span class="kw2">true</span><span class="br0">)</span>;       <span class="co1">// Checks that the response comes from the cache</span>
<span class="re0">$b</span>-&gt;<span class="me1">isCached</span><span class="br0">(</span><span class="kw2">true</span>, <span class="kw2">true</span><span class="br0">)</span>; <span class="co1">// Checks that the cached response comes with layout</span>
<span class="re0">$b</span>-&gt;<span class="me1">isCached</span><span class="br0">(</span><span class="kw2">false</span><span class="br0">)</span>;      <span class="co1">// Checks that the response doesn't come from the cache</span></pre>

<blockquote class="note"><p>
  You don't need to clear the cache at the beginning of a functional test; the bootstrap script does it for you.</p>
</blockquote>

<h3><a name="chapter_15_sub_testing_interactions_on_the_client">Testing Interactions on the Client</a></h3>

<p>The main drawback of the techniques described previously is that
they cannot simulate JavaScript. For very complex interactions, like
with Ajax interactions for instance, you need to be able to reproduce
exactly the mouse and keyboard input that a user would do and execute
scripts on the client side. Usually, these tests are reproduced by
hand, but they are very time consuming and prone to error.</p>

<p>The solution is called Selenium (<a href="http://www.openqa.org/selenium/">http://www.openqa.org/selenium/</a>),
which is a test framework written entirely in JavaScript. It executes a
set of actions on a page just like a regular user would, using the
current browser window. The advantage over the <code>sfBrowser</code> object is that Selenium is capable of executing JavaScript in a page, so you can test even Ajax interactions with it.</p>

<p>Selenium is not bundled with symfony by default. To install it, you need to create a new <code>selenium/</code> directory in your <code>web/</code> directory, and in it unpack the content of the Selenium archive (<a href="http://www.openqa.org/selenium-core/download.action">http://www.openqa.org/selenium-core/download.action</a>).
This is because Selenium relies on JavaScript, and the security
settings standard in most browsers wouldn't allow it to run unless it
is available on the same host and port as your application.</p>

<blockquote class="caution"><p>
  Be careful not to transfer the <code>selenium/</code>
directory to your production server, since it would be accessible by
anyone having access to your web document root via the browser.</p>
</blockquote>

<p>Selenium tests are written in HTML and stored in the <code>web/selenium/tests/</code>
directory. For instance, Listing 15-36 shows a functional test where
the home page is loaded, the link click me is clicked, and the text
"Hello, World" is looked for in the response. Remember that in order to
access the application in the <code>test</code> environment, you have to specify the <code>myapp_test.php</code> front controller.</p>

<p class="figure">Listing 15-36 - A Sample Selenium Test, in <code>web/selenium/test/testIndex.html</code></p>

<pre class="php">&lt;!DOCTYPE html <span class="kw2">PUBLIC</span> <span class="st0">"-//W3C//DTD HTML 4.01 Transitional//EN"</span>&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta content=<span class="st0">"text/html; charset=UTF-8"</span> http-equiv=<span class="st0">"content-type"</span>&gt;
  &lt;title&gt;Index tests&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;table cellspacing=<span class="st0">"0"</span>&gt;
&lt;tbody&gt;
  &lt;tr&gt;&lt;td colspan=<span class="st0">"3"</span>&gt;First step&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;open&lt;/td&gt;              &lt;td&gt;/myapp_test.php/&lt;/td&gt; &lt;td&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;clickAndWait&lt;/td&gt;      &lt;td&gt;link=click me&lt;/td&gt;    &lt;td&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;assertTextPresent&lt;/td&gt; &lt;td&gt;Hello, World!&lt;/td&gt;    &lt;td&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>

<p>A test case is represented by an HTML document containing a table
with three columns: command, target, and value. Not all commands take a
value, however. In this case, either leave the column blank or use <code>&amp;nbsp;</code> to make the table look better. Refer to the Selenium website for a complete list of commands.</p>

<p>You also need to add this test to the global test suite by inserting a new line in the table of the <code>TestSuite.html</code> file, located in the same directory. Listing 15-37 shows how.</p>

<p class="figure">Listing 15-37 - Adding a Test File to the Test Suite, in <code>web/selenium/test/TestSuite.html</code></p>

<pre><code>...
&lt;tr&gt;&lt;td&gt;&lt;a href='./testIndex.html'&gt;My First Test&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
...
</code></pre>

<p>To run the test, simply browse to</p>

<pre class="url"><code>http://myapp.example.com/selenium/index.html
</code></pre>

<p>Select Main Test Suite, click the button to run all tests, and watch
your browser as it reproduces the steps that you have told it to do.</p>

<blockquote class="note"><p> As Selenium tests run in a real browser,
they also allow you to test browser inconsistencies. Build your test
with one browser, and test them on all the others on which your site is
supposed to work with a single request.</p>
</blockquote>

<p>The fact that Selenium tests are written in HTML could make the
writing of Selenium tests a hassle. But thanks to the Firefox Selenium
extension (<a href="http://seleniumrecorder.mozdev.org/">http://seleniumrecorder.mozdev.org/</a>),
all it takes to create a test is to execute the test once in a recorded
session. While navigating in a recording session, you can add
assert-type tests by right-clicking in the browser window and selecting
the appropriate check under Append Selenium Command in the pop-up menu.</p>

<p>You can save the test to an HTML file to build a test suite for your
application. The Firefox extension even allows you to run the Selenium
tests that you have recorded with it.</p>

<blockquote class="note"><p>
  Don't forget to reinitialize the test data before launching the Selenium test.</p>
</blockquote>

<h2><a name="chapter_15_summary">Summary</a></h2>

<p>Automated tests include unit tests to validate methods or functions
and functional tests to validate features. Symfony relies on the lime
testing framework for unit tests and provides an <code>sfTestBrowser</code>
class especially for functional tests. They both provide many assertion
methods, from basic to the most advanced, like CSS selectors. Use the
symfony command line to launch tests, either one by one (with the <code>test-unit</code> and <code>test-functional</code> tasks) or in a test harness (with the <code>test-all</code> task). When dealing with data, automated tests use fixtures and stubs, and this is easily achieved within symfony unit tests.</p>

<p>If you make sure to write enough unit tests to cover a large part of
your applications (maybe using the TDD methodology), you will feel
safer when refactoring internals or adding new features, and you may
even gain some time on the documentation task.</p>


  <div style="padding: 15px 0pt;">
    <div style="float: right;">
              <a href="http://www.symfony-project.org/book/1_0/16-Application-Management-Tools">Chapter 16 - Application Management Tools</a>&nbsp;»
          </div>

    <div style="float: left;">
              «&nbsp;<a href="http://www.symfony-project.org/book/1_0/14-Generators">Chapter 14 - Generators</a>          </div>
  </div>
<div class="feedback">
  <h3>Questions &amp; Feedback</h3>
    <p>
      If you find a typo or an error, please
      <a href="http://www.symfony-project.com/user/new">register</a>
      and <a href="http://trac.symfony-project.org/newticket">open a ticket</a>.
    </p>
    <p>
      If you need support or have a technical question, please post to the official
      <a href="http://groups.google.com/group/symfony-users">user mailing-list</a>.</p>
</div>
    </div>

    <div id="footer">
      Powered by <a href="http://www.symfony-project.org/"><img alt="Symfony" src="15-Unit-and-Functional-Testing_files/symfony_button.gif" align="middle"></a>&nbsp;-&nbsp;
      <a href="http://www.symfony-project.org/donate">Make a donation</a>&nbsp;-&nbsp;
      "symfony" is a trademark of Fabien Potencier. All rights reserved.
    </div>

        <div class="sensiolabs">
  <div class="id_content">
    <div class="clearfix">
      <div class="column column_01">
        <h2><img src="15-Unit-and-Functional-Testing_files/network.gif" alt="The Sensio Labs Network"></h2>
        <div class="introduction">

          Since 1998, Sensio Labs has been promoting the Open-Source software
          movement by providing quality web application development, training,
          consulting, and supporting several large Open-Source projects.

        </div>
      </div>
      <div class="column column_02">
        <div class="box">
          <h3>Open-Source Products</h3>
          <ul>
            <li><a href="http://www.symfony-project.org/">Symfony MVC framework</a></li>
            <li><a href="http://components.symfony-project.org/">Symfony Components</a></li>
            <li><a href="http://www.doctrine-project.org/">Doctrine ORM</a></li>
            <li><a href="http://www.swiftmailer.org/">Swift Mailer</a></li>
          </ul>
        </div>
      </div>
      <div class="column column_03">
        <div class="box">
        <h3>Services</h3>
          <ul>
            <li><a href="http://trainings.sensiolabs.com/">Be Trained by OSS experts</a></li>
            <li><a href="http://books.sensiolabs.com/">Books on OSS</a></li>
            <li><a href="http://www.symfony-live.com/">The Symfony Live Conference</a></li>
          </ul>
          <p class="more"><a href="http://www.sensiolabs.com/">&gt; More</a></p>
        </div>
      </div>
    </div>
    <div class="logo"><a href="http://www.sensiolabs.com/"><img src="15-Unit-and-Functional-Testing_files/sensiolabs.gif" alt="Sensio Labs"></a></div>
  </div>
</div>

          <script type="text/javascript">
      var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
      document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
      </script><script src="15-Unit-and-Functional-Testing_files/ga.js" type="text/javascript"></script>
      <script type="text/javascript">
      var pageTracker = _gat._getTracker("UA-89393-1");
      pageTracker._initData();
      pageTracker._trackPageview();
      </script>
    
  </body></html>